#!/usr/bin/env python
#-*-encoding:utf-8-*-
#
# Twitto - The Twitter XMPP BOT
# Copyright (C) 2009 Renê de Souza Pinto
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import sys
import signal
import getopt
import xmpp
import re

import twitter
import datetime


VERSION = "0.1 beta"


##
# Class brain
#
# Implement the BOT commands
#
class brain():

	def __init__(self, user, resource):

		self.user      = user
		self.resource  = resource
		self.logged    = False
		self.twapi     = None
		self.twuser    = ""        # Twitter username
		self.twpass    = ""        # Twitter password
		self.followers = []        # Twitter followers
		self.following = []        # Twitter following

		self.regxp1   = re.compile(" {0,}#(\w+)")
		self.regxp2   = re.compile("\".*\"|\'.*\'|[^ ]+")

	##
	# Proccess message from user
	#
	def proccess_msg(self, msg):

		txtmsg = msg.getBody()

		if txtmsg == None:
			return None

		parse = self.regxp1.split(txtmsg)

		if len(parse) <= 1:
			return "Master, you need to pass a command to me. Please, send #help to more information."
		else:
			argstr = parse[2].strip()
			args   = self.regxp2.findall(argstr)

		# Check and execute command
		if parse[1] == "help":
			return self.help(msg, args)
		elif parse[1] == "version":
			return self.version(msg, args)
		elif parse[1] == "time":
			return self.time(msg, args)
		elif parse[1] == "date":
			return self.date(msg, args)
		elif parse[1] == "login":
			return self.login(msg, args)
		elif parse[1] == "post":
			return self.post(msg, args)
		elif parse[1] == "followers":
			return self.get_followers(msg, args)
		elif parse[1] == "following":
			return self.get_following(msg, args)
		elif parse[1] == "dmsg":
			return self.dmsg(msg, args)
		elif parse[1] == "follow":
			return self.follow(msg, args)
		elif parse[1] == "unfollow":
			return self.unfollow(msg, args)
		elif parse[1] == "timeline":
			return self.timeline(msg, args)
		elif parse[1] == "ftimeline":
			return self.ftimeline(msg, args)
		elif parse[1] == "disconnect":
			return self.exit(msg, args)
		else:
			return (parse[1] + " -- invalid command. Send #help to get avaliable commands.")


	##
	# BOT commands
	##

	##
	# help
	#
	def help(self, msg, args=[]):

		# Build commands list
		commands = {}

		commands['help']       = "Show help information"
		commands['version']    = "Show Twitto version"
		commands['time']       = "Show current time server"
		commands['date']       = "Show date server"
		commands['login']      = "Connect to twitter (#login <username> <password>)"
		commands['post']       = "Post on your twitter (#post <message>)"
		commands['followers']  = "List who's following you"
		commands['following']  = "List who you're following"
		commands['dmsg']       = "Send direct message to a friend (#dmsg <friend> <message>)"
		commands['follow']     = "Follow a user (#follow <username>)"
		commands['unfollow']   = "Unfollow a user (#unfollow <username>)"
		commands['timeline']   = "Get user timeline (#timeline [username] [count])"
		commands['ftimeline']  = "Get user's friends timeline (#timeline [username] [count])"
		commands['disconnect'] = "Disconnect from twitter server"

		# Check for arguments
		if len(args) == 1:
			if commands.has_key(args[0]):
				helpstr = "\n" + args[0] + " -- " + commands[args[0]] + "\n"
			else:
				helpstr = "\n" + args[0] + " -- invalid command\n"
			return helpstr
		elif len(args) > 1:
			return "\nYou need to pass just one argument (or none to see the complete help)."

		# Build help
		helpstr  = "\nTwitto - The Twitter XMPP BOT\n"
		helpstr += "The commands avaliable are:\n\n"
		for com in commands:
			helpstr += com + " -- " + commands[com] + "\n"

		helpstr += "\nTo use a command send #command. Example: #help\n"

		return helpstr


	##
	# version
	#
	def version(self, msg, args=[]):

		rstr = "Twitto Version: " + VERSION
		return rstr


	##
	# time
	#
	def time(self, msg, args=[]):

		today = datetime.datetime.now()
		return today.strftime("%H:%M:%S")


	##
	# date
	#
	def date(self, msg, args=[]):

		today = datetime.datetime.now()
		return today.ctime()


	##
	# login
	#
	def login(self, msg, args=[]):

		if len(args) != 2:
			rstr  = "\n:( You need to pass your twitter username and password.\n"
			rstr += "Usage: #login <username> <password>\n"
			rstr += "Example: #login foo bar\n"
			return rstr

		# Check login
		if self.logged:
			return "\nYou are already logged on twitter. Use command #disconnect to disconnect from twitter server."

		# Connect to twitter
		self.twuser = args[0]
		self.twpass = args[1]
		self.twapi  = twitter.Api(self.twuser, self.twpass)

		# Try to receive the followers list
		rstr = ""
		try:
			self.followers = self.twapi.GetFollowers()
			self.logged    = True
			rstr  = "\n:) Connected. You are following " + str(len(self.followers)) + " people.\n"
			rstr += "Now you can post on your twitter using #post <message>"
		except:
			rstr = "\n:( Could not connect to your twitter. Check your username and password."

		return rstr


	##
	# post
	#
	def post(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter. Please, use command #login first."

		if len(args) == 0:
			return ":( Please, pass a message to post."

		# Post message
		message = msg.getBody()
		message = message.strip()
		message = message[5:].strip()

		rstr = ""
		try:
			self.twapi.PostUpdate(message)
			rstr = ":) Message posted!"
		except:
			rstr = ":( Error. Your message could not be posted. Sorry."

		return rstr


	##
	# get_followers
	#
	def get_followers(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		try:
			self.followers = self.twapi.GetFollowers()
		except:
			return "\n:( Could not retrieve followers list from twitter."

		rstr = "People who are following you:\n"
		for people in self.followers:

			rstr += people.name + " (" + people.screen_name + "), "

		return rstr[:len(rstr)-2]


	##
	# get_following
	#
	def get_following(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		try:
			self.following = self.twapi.GetFriends()
		except:
			return "\n:( Could not retrieve following list from twitter."

		rstr = "People you're following:\n"
		for friend in self.following:

			rstr += friend.name + " (" + friend.screen_name + "), "

		return rstr[:len(rstr)-2]


	##
	# dmsg
	#
	def dmsg(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		if len(args) < 2:
			rstr  = "\n:( You need to pass your friend username and the message.\n"
			rstr += "Usage: #dmsg <friend> <message>\n"
			rstr += "Example: #dmsg jack Hi jack!\n"
			return rstr

		# Get message
		message = msg.getBody()
		message = message.strip()
		message = message[7 + len(args[0]):].strip()

		rstr = ""
		try:
			self.twapi.PostDirectMessage(args[0], message)
			rstr = ":) Message posted!"
		except:
			rstr = ":( Error. Your message could not be posted. Sorry."

		return rstr


	##
	# follow
	#
	def follow(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		if len(args) < 1:
			rstr  = "\n:( You need to pass your friend username.\n"
			rstr += "Usage: #follow <username>\n"
			return rstr

		rstr = ""
		try:
			self.twapi.CreateFriendship(args[0])
			rstr = ":) Now you are follow " + args[0] + "!"
		except:
			rstr = ":( An Error was ocurred. Sorry."

		return rstr


	##
	# unfollow
	#
	def unfollow(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		if len(args) < 1:
			rstr  = "\n:( You need to pass the username.\n"
			rstr += "Usage: #unfollow <username>\n"
			return rstr

		rstr = ""
		try:
			self.twapi.DestroyFriendship(args[0])
			rstr = ":) Now you are not follow " + args[0] + " !"
		except:
			rstr = ":( An Error was ocurred. Sorry."

		return rstr


	##
	# timeline
	#
	def timeline(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		argc  = len(args)
		count = 50
		user  = None

		if argc >= 1:
			user = args[0]
			if argc >= 2:
				try:
					count = int(args[1])
				except:
					return ":( You need to pass a integer argument to count."

		print "DEBUG",user,"--",count

		rstr = "\n"
		try:
			tline = self.twapi.GetUserTimeline(user, count)
			for status in tline:
				rstr += status.user.screen_name + ": " + status.text + "\n-------------------------------\n"
		except:
			rstr = ":( An Error was ocurred. Sorry."

		return rstr


	##
	# ftimeline
	#
	def ftimeline(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		argc  = len(args)
		count = 50
		user  = None

		if argc >= 1:
			user = args[0]
			if argc >= 2:
				try:
					count = int(args[1])
				except:
					return ":( You need to pass a integer argument to count."

		rstr = "\n"
		try:
			tline = self.twapi.GetFriendsTimeline(user, count)
			for status in tline:
				rstr += status.user.screen_name + ": " + status.text + "\n-------------------------------\n"
		except:
			rstr = ":( An Error was ocurred. Sorry."

		return rstr


	##
	# disconnect
	#
	def exit(self, msg, args=[]):

		if not self.logged:
			return ":( You are not connected on twitter."

		del(self.twapi)
		self.logged = False
		return ":) Disconnected."



##
# Class bot
#
# Implement the BOT control over messages
#
class bot():

	def __init__(self):

		# Connected users
		self.users_list = {}

		signal.signal(signal.SIGTERM, self.sigtermHandler)

	##
	# presenceHandler
	#
	def presenceHandler(self, conn, pres):

		jid  = pres.getFrom()
		type = pres.getType()

		if type == "subscribe":
			# Accept
			reply = xmpp.protocol.Presence(jid, "subscribed")
			conn.send(reply)
		elif type == "unsubscribe":
			# Remove user
			reply = xmpp.protocol.Presence(jid, "unsubscribed")
			conn.send(reply)


	##
	# messageHandler
	#
	def messageHandler(self, conn, msg):

		user = msg.getFrom().getNode()

		if not self.users_list.has_key(user):
			# New user
			if not user == None:
				self.users_list[user] = brain(user, "DEBUG")

		bclass = self.users_list[user]
		if bclass != None:
			reply  = bclass.proccess_msg(msg)
			# Send result
			conn.send(xmpp.Message(msg.getFrom(), reply))


	##
	# StepOn
	#
	def StepOn(self, conn):

		try:
			conn.Process(1)
		except KeyboardInterrupt:
			print "Exiting..."
			return 0

		return 1


	##
	# sigtermHandler
	#
	def sigtermHandler(self, signum, frame):

		raise KeyboardInterrupt


##
# start_bot
#
def start_bot(user="", password="", server="", port=-1):

	jid_str = user + '@' + server
	jid     = xmpp.JID(jid_str)

	conn    = xmpp.Client(server, debug=[])
	connres = conn.connect()

	if not connres:
		print "Unable to connect with %s" % server
		sys.exit(1)
	elif connres != 'tls':
		print "Warning: Unable to estabilish secure connection"

	auth = conn.auth(user, password, "Bot is ON - TESTMODE")

	if not auth:
		print "Login failed: Unable to authorize %s on %s" % (user, server)
		sys.exit(1)


	twitto = bot()

	print "Connected."
	conn.RegisterHandler('message', twitto.messageHandler)
	conn.RegisterHandler('presence', twitto.presenceHandler)
	conn.sendInitPresence()

	print "BOT started!"
	while twitto.StepOn(conn): pass

	conn.disconnect()
	print "Disconnected."


##
# show_help
#
def show_help():

	print "Twitto - The Twitter XMPP BOT\n"
	print "Usage: %s <server> <username> <password> [port]\n" % sys.argv[0]
	print "       -h | --help     : Show this help"
	print "       -v | --version  : Show program version"
	print "       -s | --server   : XMPP server"
	print "       -t | --port     : XMPP server port"
	print "       -u | --username : username to XMPP server"
	print "       -p | --password : password to XMPP server\n"
	print "Example: %s --username=foo --password=bar --server=jabber.org\n" % sys.argv[0]


##
# show_version
#
def show_version():

	print "Twitto - The Twitter XMPP BOT\n"
	print "Version: %s" % VERSION


##
# Main
#
def main():
	# Commands avaliable
	commands = ["help", "version", "server=", "port=", "username=", "password="]

	try:
		opts, args = getopt.getopt(sys.argv[1:], "hvs:t:u:p:", commands)
	except getopt.GetoptError, err:
		show_help()
		print str(err)
		sys.exit(2)


	server   = None
	port     = -1
	user     = None
	password = None

	# Parse command line
	for opt, arg in opts:

		if opt in ("-h", "--help"):
			show_help()
			sys.exit()
		elif opt in ("-v", "--version"):
			show_version()
			sys.exit()
		elif opt in ("-s", "--server"):
			server   = arg
		elif opt in ("-t", "--port"):
			try:
				port = int(arg)
			except:
				show_help()
				print "--port: You need to pass an integer for this argument"
		elif opt in ("-u", "--username"):
			user     = arg
		elif opt in ("-p", "--password"):
			password = arg
		else:
			assert False, "invalid option"

	if server == None or password == None or server == None:
		show_help()
	else:
		start_bot(user, password, server, port)

## main
if __name__ == "__main__":

	main()
